/**
 * Copyright (c) 2012-2017, www.tinygroup.org (luo_guo@icloud.com).
 * <p>
 * Licensed under the GPL, Version 3.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.gnu.org/licenses/gpl.html
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.tinygroup.service.config;

import org.apache.commons.collections.map.HashedMap;
import org.tinygroup.commons.beanutil.BeanUtil;
import org.tinygroup.commons.serviceid.ClassMethodServiceIdGenerateRule;
import org.tinygroup.commons.serviceid.ServiceIdGenerateRule;
import org.tinygroup.commons.tools.ArrayUtil;
import org.tinygroup.commons.tools.CollectionUtil;
import org.tinygroup.commons.tools.StringUtil;
import org.tinygroup.config.Configuration;
import org.tinygroup.event.Parameter;
import org.tinygroup.fileresolver.impl.AbstractFileProcessor;
import org.tinygroup.logger.LogLevel;
import org.tinygroup.logger.Logger;
import org.tinygroup.logger.LoggerFactory;
import org.tinygroup.service.ServiceProxy;
import org.tinygroup.service.exception.ServiceLoadException;
import org.tinygroup.service.loader.ServiceLoader;
import org.tinygroup.service.registry.ServiceRegistry;
import org.tinygroup.service.registry.ServiceRegistryItem;
import org.tinygroup.service.release.ServiceReleaseManager;
import org.tinygroup.service.util.ServiceUtil;
import org.tinygroup.xmlparser.node.XmlNode;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class XmlConfigClassServiceLoader extends XmlConfigServiceLoader{
       private static final Logger LOGGER = LoggerFactory.getLogger(XmlConfigClassServiceLoader.class);
       private Map<String,ServiceComponent> tempComponents = new HashMap<String, ServiceComponent>();
       private ServiceIdGenerateRule serviceIdGenerateRule;

        protected void registerServices(Object instance,
                                        ServiceComponent component, ServiceRegistry serviceRegistry, ClassLoader classLoader)
                throws ClassNotFoundException, ServiceLoadException {
            if(CollectionUtil.isEmpty( component.getServiceMethods())){
                generateServiceMethod(instance, component);
            }
            super.registerServices(instance,component,serviceRegistry,classLoader);
        }

        private void generateServiceMethod(Object instance, ServiceComponent component) throws ServiceLoadException {
            List<ServiceMethod> serviceMethods = new ArrayList<ServiceMethod>();
            Map<String, Object> methodNameMap = new HashedMap();
            Class<?>  clazz = getClazz(component.getType(),instance);
            Method[] methods = getClassMethods(clazz);
            for (Method method : methods) {
                Method realMethod = getRealMethod(instance, method);
                String methodName = getMethodName(realMethod.getName(), methodNameMap);

                ServiceMethod serviceMethod = new ServiceMethod();
                serviceMethod.setServiceId(getServiceIdGenerateRule().generateServiceId(clazz,method));
                serviceMethod.setLocalName(methodName);
                serviceMethod.setMethodName(realMethod.getName());

                initInputParameters(realMethod, serviceMethod);
                initOutputParameters(realMethod, serviceMethod);

                serviceMethods.add(serviceMethod);
            }
            component.setServiceMethods(serviceMethods);
            tempComponents.put(component.getType(), component);
        }

        private Method[] getClassMethods(Class<?> clazz) {
            if(clazz.isInterface()) {
                return clazz.getMethods();
            }
            List<Method> list = new ArrayList<Method>();
            Method[] methods = clazz.getMethods();
            for(Method m:methods) {
                if (Object.class.getName().equals(m.getDeclaringClass().getName())) {
                    continue;
                }
                list.add(m);
            }
            return list.toArray(new Method[list.size()]);
        }

        private Class<?> getClazz(String classPath,Object instance) throws ServiceLoadException {
                if(StringUtil.isBlank(classPath)){
                    return instance.getClass();
                }
                try {
                    return Class.forName(classPath);
                } catch (ClassNotFoundException e) {
                    LOGGER.errorMessage("服务发布接口不存在,接口路径:{}", e, classPath);
                    throw new ServiceLoadException(e);
                }


        }
        public void removeServiceComponents(ServiceRegistry serviceRegistry,
                                            ServiceComponents serviceComponents) {
            for (ServiceComponent component : serviceComponents
                    .getServiceComponents()) {
                String type = component.getType();
                List<ServiceMethod> list = component.getServiceMethods();
                if(tempComponents.containsKey(type)){
                    list = tempComponents.remove(type).getServiceMethods();
                }
                for (ServiceMethod method : list) {
                    serviceRegistry.removeService(method.getServiceId());
                }

            }
        }
        private Method getRealMethod(Object instance, Method method) throws ServiceLoadException {
            try {
                return instance.getClass().getMethod(method.getName(), method.getParameterTypes());
            } catch (NoSuchMethodException e) {
                LOGGER.errorMessage("接口：{} 方法：{} 找不到实现", e, instance.getClass().getName(), method.getName());
                throw new ServiceLoadException(e);
            }
        }
        private String getMethodName(String methodName, Map<String, Object> methodNameMap) {
            String name = methodName;
            int index = 1;
            do {
                if (methodNameMap.containsKey(name)) {
                    name += index;
                    index++;
                } else {
                    methodNameMap.put(name, null);
                }
            } while (!methodNameMap.containsKey(name));

            return name;
        }
        private void initInputParameters(Method method, ServiceMethod serviceMethod) {
            List<ServiceParameter> serviceParameters = new ArrayList<ServiceParameter>();
            serviceMethod.setServiceParameters(serviceParameters);
            Class[] types = method.getParameterTypes();
            Type[] types1 = method.getGenericParameterTypes();
            String[] parameterNames = BeanUtil.getMethodParameterName(method);
            if(parameterNames==null){
                return;
            }
            for (int i = 0; i < types.length; i++) {
                Class<?> type = types[i];
                ServiceParameter parameter = new ServiceParameter();
                parameter.setRequired(false);
                parameter.setName(parameterNames[i]);
                if (type.isArray()) {//数组
                    parameter.setType(type.getCanonicalName().replace("[", "").replace("]", "").trim());
                    parameter.setArray(true);
                } else if (type.isInterface() && type.isAssignableFrom(List.class)) {//集合
                    parameter.setArray(false);
                    ParameterizedTypeImpl typeImpl = (ParameterizedTypeImpl) types1[i];
                    Class typeClass = (Class) typeImpl.getActualTypeArguments()[0];
                    parameter.setType(typeClass.getName());
                    parameter.setCollectionType(type.getName());
                } else {
                    parameter.setType(type.getCanonicalName());
                    parameter.setArray(false);
                }
                serviceParameters.add(parameter);
            }

        }
        private void initOutputParameters(Method method, ServiceMethod serviceMethod) {
            ServiceParameter serviceResult = new ServiceParameter();
            serviceResult.setRequired(false);
            serviceResult.setName("result");
            if (void.class.equals(method.getReturnType())) {
                serviceResult.setType("void");
                serviceResult.setArray(false);
            } else {
                if (method.getReturnType().isArray()) {
                    serviceResult.setType(method.getReturnType().getCanonicalName().replace("[", "").replace("]", "").trim());
                    serviceResult.setArray(true);
                } else if (method.getReturnType().isInterface() && method.getReturnType().isAssignableFrom(List.class)) {
                    serviceResult.setArray(false);
                    serviceResult.setCollectionType(method.getReturnType().getName());
                    ParameterizedTypeImpl typeImpl = (ParameterizedTypeImpl) method.getGenericReturnType();
                    Class typeClass = (Class) typeImpl.getActualTypeArguments()[0];
                    serviceResult.setType(typeClass.getName());
                } else {
                    serviceResult.setArray(false);
                    serviceResult.setType(method.getReturnType().getName());
                }
            }
            serviceMethod.setServiceResult(serviceResult);
        }


        public ServiceIdGenerateRule getServiceIdGenerateRule() {
            if(serviceIdGenerateRule==null){
                serviceIdGenerateRule = new ClassMethodServiceIdGenerateRule();
            }
            return serviceIdGenerateRule;
        }

        public void setServiceIdGenerateRule(ServiceIdGenerateRule serviceIdGenerateRule) {
            this.serviceIdGenerateRule = serviceIdGenerateRule;
        }
}
